/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/phone/SpipcHandler.cpp,v 1.17 2003/11/21 16:12:58 itsme Exp $
 *
 *
 * this handles all messages to and from the machine,
 * messages are translated in method calls to PhoneHandler, or UserDataHandler.
 *
 * dial messages are directly handled, and only have influence on the state of
 * the dialog. this should be fixed, and also be handled by the PhoneHandler.
 *
 */

#include "CPhone.h"         // include first, to avoid 'new' error with stl

#include "SpipcHandler.h"
#include "ConfigManager.h"

#include "spif.h"
#include "debug.h"
#include "CPhoneDlg.h"
#include "PhoneOptions.h"

#include "DiffieHellmanHandler.h"
#include "PhoneHandler.h"
#include "VolumeManager.h"

#include "AudioDevice.h"

// misc utils
#ifdef _WIN32_WCE
#include "hidewindow.h"     // HideWindowByTitle
#include "SysContacts.h"
#endif

#include <string>
#include "stringutils.h"
#include "vectorutils.h"


SpipcHandler::SpipcHandler(CCPhoneDlg& dlg) 
    : m_dlg(dlg)
{
    m_dlg.SetStatus( "idle" );

    m_nRingCount= 0;
    m_bGotCallerid= false;
}
SpipcHandler::~SpipcHandler() 
{
}

void SpipcHandler::SetConfiguration(int cfgtype)
{
    SP_initPhone(cfgtype);
}

void SpipcHandler::UpdateLastCaller(const std::string& nr)
{
    std::string name;

#ifdef _WIN32_WCE
    SysContactsEntry entry;
    
    SysContacts ctx;  ctx.Open();
    if (ctx.FindEntryByNumber(ToWString(nr).c_str(), entry))
    {
        name=  ToString(entry.name);

        // todo: move this to just after connect, and store it in our own db, not the systemdb.
        //ctx.AddDataCallEntry(ToWString(nr).c_str());
    }
#endif

    m_dlg.SetNumber(nr);
    m_dlg.SetPartnerName(name);
}

// parameter contains the hash of the diffiehellman result.
bool SpipcHandler::SetMachineKey(ByteVector keydata, bool bOriginating) 
{
    if (keydata.size()!=32)
    {
        debug("ERROR: SetMachineKey: invalid keysize\n");
        return false;
    }
    int rce= SP_setEncoderKey(keydata, bOriginating);
    int rcd= SP_setDecoderKey(keydata, bOriginating);

    debug("setenc:%d setdec:%d\n", rce, rcd);

    return rce==E_SPIPC_OK && rcd==E_SPIPC_OK;
}

// todo:
//    keep track of dwId's, and report back status of command to the correct handler.
//
//    plan: with each spipc ( and ril ) request, pass a function that
//    handles the answer.

// handle message from spipc, sent via a windows message to the main dialog
bool SpipcHandler::HandleMessage(DWORD lParam, DWORD wParam)
{
    int res;
    WORD nCmd;
    DWORD dwId;
    WORD nDataLen;
    void *pData;

    res= SP__getMessage( &nCmd, &dwId, &nDataLen, &pData );
    if( res!=E_SPIPC_OK )
    {
        debug("ERROR retrieving message\n");
        return false;
    }
    debug("<<< %3d %hs\n", dwId, SPIPCCommandString(nCmd));

    switch( nCmd )
    {
    case SPIPC_RES_ERROR:
        {
            int res= SP_getError( (SPIPC_MSG_RES_ERROR*)pData );
            debug("SPIPC_RES_ERROR %hs\n", SPIPCResult(res));
        }

        // todo: abort action here... mmm how do I know who to send the error to....
        //       currently not really keeping track of owners.
        break;

    case SPIPC_RES_OK:

        // next step in state machine...
        break;

    case SPIPC_RES_PONG:
        break;

    case SPIPC_EVT_RINGON:
        theApp.m_phone->SignalPhoneRingOn();

        // make sure previous caller does not remain on screen when there is no callerid.
        m_dlg.ClearPartner();

        m_bGotCallerid= false;
        m_nRingCount= 0;
        break;
    case SPIPC_EVT_RING:
        debug("RING: cid=%d rcnt=%d\n", m_bGotCallerid, m_nRingCount);

#ifdef _WIN32_WCE
        // try hiding other phone app window here.
        //  ( in evt_ringon, it is too early, the other phoneapp
        //  has not woken up yet )
        HideWindowByTitle(L"Phone - Incoming...");
#endif
        if (++m_nRingCount==2 && !m_bGotCallerid)
            m_dlg.SetPartnerUnknown();
        break;

    case SPIPC_EVT_RINGOFF:
        theApp.m_phone->SignalPhoneRingOff();
        break;

    case SPIPC_EVT_CALLERID:
        UpdateLastCaller(SP_getCallerId( (const SPIPC_MSG_EVT_CALLERID*)pData ));
        m_bGotCallerid= true;
        break;

    case SPIPC_RES_DISCONNECT:
        {
        SPIPC_MSG_RES_DISCONNECT *msg= (SPIPC_MSG_RES_DISCONNECT*)pData;
        debug("      reason=%s result=%s\n", SPIPCDisconnectReason(msg->reason), SPIPCResult(msg->result));

        // todo: in old version of spipc the reason was in the result field.
        //      have to check if the correcting of this has any consequences
        //      for this code.
        theApp.m_phone->SignalPhoneDisconnect(theApp.m_phone->FindDisconnectReason(msg->reason, msg->result));
        }
        break;

    case SPIPC_RES_CONNECT:
        // parameters are: 
        //     - dialout    bool - I am dialing out
        //     - media      SPIPC_LINKMEDIA_*  { TEL|IP|UNKNOWN }
        //     - service    K_TAPI_LINKSERVICE_* { DATA|FAX|VOICE|UNKNOWN }
        //                  K_HAYES_LINKSERVICE_* { DATA|FAX|VOICE|UNKNOWN }
        //     - baudrate
        //     - carrier    K_TAPI_LINKCARRIER_* { DIG|ANA|UNKNOWN },
        //                  K_HAYES_LINKCARRIER_* { DIG_V110|ANA_V32|UNKNOWN  }
        //     - protocol   K_HAYES_LINKPROTOCOL_* { RAW|REL|RELCOMP|UNKNOWN }
        //     - remnum
        //
        // since these are all irrelevant, I won't pass them on.
        theApp.m_phone->SignalPhoneConnect();
        break;

    case SPIPC_EVT_HANGUP:
        theApp.m_phone->SignalPhoneRemoteHangup();
        break;

    case SPIPC_PRO_NEWSTATE:
        // ?? never received
        break;
    case SPIPC_PRO_HANGUPSTART:
        // ?? never received
        break;
    case SPIPC_PRO_ANSWERSTART:
        // received about 1 second after SPIPC_CMD_ANSWER
        theApp.m_phone->SignalPhoneAnswerStart();
        break;
    case SPIPC_PRO_DIALSTART:
        // received about 180-800 ms after SPIPC_CMD_DIAL
        theApp.m_phone->SignalPhoneDialStart();
        break;

    case SPIPC_MUX_RXUSERDATA:
        {
            SPIPC_MSG_MUX_RXUSERDATA *muxrx= (SPIPC_MSG_MUX_RXUSERDATA*) pData;
            debug("received userdata: datalen=%d, hdr.len=%d  sizeof(hdr)=%d\n", 
                nDataLen, muxrx->hdr.len, sizeof(SPIPC_HEADER));

            // '-1' : for the 'phone' field
            theApp.m_ud->HandleUserData(dwId, ByteVector(muxrx->userdata, muxrx->userdata+nDataLen-sizeof(SPIPC_HEADER)-1));
        }
        break;
    case SPIPC_MUX_TXUSERDATA:
        {
            SPIPC_MSG_MUX_TXUSERDATA *muxtx= (SPIPC_MSG_MUX_TXUSERDATA*)pData;
            debug("mux-tx: state=%d\n", muxtx->state);
        }
        break;
    case SPIPC_PRT_RXSYNC:
        {
            SPIPC_MSG_PRT_RXSYNC *rxsync= (SPIPC_MSG_PRT_RXSYNC *)pData;
            debug("SYNCSTATE=%hs\n", SPIPCSyncStateName(rxsync->sync));
            if (rxsync->sync == SPIPC_SYNC_INSIDE)
                theApp.m_dh->HandleSyncFound();
            else
                theApp.m_dh->HandleSyncLoss();
        }
        break;
    case SPIPC_RES_GETOUTVOL:
        {
            SPIPC_MSG_RES_GETOUTVOL *vol= (SPIPC_MSG_RES_GETOUTVOL*)pData;
            debug("OUTVOL: phone=%d chan=%d vol=%d\n", vol->phone, vol->chan, vol->volume);

            theApp.m_volmgr->UpdateCurrentVolume(vol->volume);
        }
        break;
    default:
        debug("ignoring sp message %04x\n", nCmd);

    }

    if( pData )
        LocalFree( pData );

    return true;
}


bool SpipcHandler::Init()
{
    int res= SP_initSystem(m_dlg.m_hWnd);
    if (res != E_SPIPC_OK)
    {
        debug("SP_initSystem error %08lx : %hs\n", res, SPIPCResult(res));
        return false;
    }

    return true;
}

bool SpipcHandler::Stop()
{
    SP_stopSystem();

    return true;
}
bool SpipcHandler::Dial_Digital(const std::string& number )
{
    int res= SP_dial_gsm(SPIPC_LINKCARRIER_DIG, number);
    if (res != E_SPIPC_OK)
    {
        debug("SP_dial_gsm(DIG) error %08lx : %hs\n", res, SPIPCResult(res));
        return false;
    }

    return true;
}
bool SpipcHandler::Dial_Analog( const std::string& number )
{
    int res= SP_dial_gsm(SPIPC_LINKCARRIER_ANA, number);
    if (res != E_SPIPC_OK)
    {
        debug("SP_dial_gsm(ANA) error %08lx : %hs\n", res, SPIPCResult(res));
        return false;
    }
    return true;
}


bool SpipcHandler::Hangup()
{
    int res= SP_hangup();
    if (res != E_SPIPC_OK)
    {
        debug("SP_hangup error %08lx : %hs\n", res, SPIPCResult(res));
        return false;
    }
    return true;
}

bool SpipcHandler::Answer()
{
    int res= SP_answer();
    if (res != E_SPIPC_OK)
    {
        debug("SP_answer error %08lx : %hs\n", res, SPIPCResult(res));
        return false;
    }
    return true;
}

bool SpipcHandler::MuteVoice()
{
    SP_setEncoderMode(SPIPC_MODE_SILENT);
    return true;
}
bool SpipcHandler::UnmuteVoice()
{
    SP_setEncoderMode(SPIPC_MODE_VOICE);
    return true;
}

bool SpipcHandler::DisableVoiceCodec()
{
    SP_setEncoderMode(SPIPC_MODE_SILENT);
    SP_setDecoderMode(SPIPC_MODE_SILENT);
    return true;
}
bool SpipcHandler::EnableVoiceCodec()
{
    SP_setEncoderMode(g_options.start_muted?SPIPC_MODE_SILENT:SPIPC_MODE_VOICE);
    SP_setDecoderMode(SPIPC_MODE_VOICE);
    return true;
}


static AudioDevice g_au;

bool SpipcHandler::SetVolume(DWORD volume)
{
#ifdef __USE_SPIPC_FOR_VOLUME
    return E_SPIPC_OK==SP_setVolume(volume);
#else
    if (!g_au.SetVolume(volume))
        return false;

    debug("SETVOLUME = %04x\n", volume);
    return true;
#endif

}

// answer is always reported back to the volumemanager.
bool SpipcHandler::RequestVolume()
{
#ifdef __USE_SPIPC_FOR_VOLUME
    return E_SPIPC_OK==SP_getVolume();
#else

    DWORD volume;

    if (!g_au.GetVolume(volume))
        return false;

    debug("GETVOLUME = %04x\n", volume);
    theApp.m_volmgr->UpdateCurrentVolume(volume);

    return true;
#endif
}

bool SpipcHandler::DoMakeCall()
{
    std::string number;

    // find number to call, either recently entered number
    // or otherwise most recently connectedd number.
    if (!m_dlg.GetNumber(number))
    {
        return false;
    }

    bool bRes= false;

    if (theApp.m_cfgmgr->m_nCallType==ConfigManager::CALLTYPE_DIGITAL )
        bRes= Dial_Digital(number);
    else if (theApp.m_cfgmgr->m_nCallType==ConfigManager::CALLTYPE_ANALOG )
        bRes= Dial_Analog(number);
    else
        debug("ERROR: unsupported calltype\n");

    //UpdateLastCaller(number);

    return bRes;
}

// this watches if the machine is still up, currently
// there are several problems, probably in TAPI, which cause
// the machine to crash at certain moments.
bool SpipcHandler::TestForOrRestartMachine()
{
    if (MachineIsRunning())
        return true;
    debug("*************** MACHINE CRASHED *****************\n");
    if (!FindAndStartMachine())
        return false;
    debug("*************** MACHINE RESTARTED *****************\n");

    if (theApp.m_cfgmgr)
        theApp.m_cfgmgr->Init();

    return true;
}
